package org.yunai.swjg.server.module.scene;

import org.slf4j.Logger;
import org.springframework.stereotype.Service;
import org.yunai.swjg.server.core.annotation.MainThread;
import org.yunai.swjg.server.core.annotation.SceneThread;
import org.yunai.swjg.server.core.service.*;
import org.yunai.swjg.server.module.player.vo.Player;
import org.yunai.swjg.server.module.scene.callback.PlayerSwitchNormalSceneCallback;
import org.yunai.swjg.server.module.scene.core.AbstractScene;
import org.yunai.swjg.server.module.scene.core.AbstractSceneService;
import org.yunai.swjg.server.module.scene.core.SceneFactory;
import org.yunai.swjg.server.module.scene.core.SceneRunner;
import org.yunai.swjg.server.module.scene.core.template.SceneTemplate;
import org.yunai.swjg.server.module.scene.msg.SysPlayerEnterNormalScene;
import org.yunai.yfserver.common.LoggerFactory;
import org.yunai.yfserver.message.sys.SysInternalMessage;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 普通场景Service<br />
 * 每个场景最多1个线。<br />
 * 当玩家进入场景时，并且场景不存在时，则新开该场景对应的{@link SceneRunner}。<br />
 * 当场景中没人时，则进行场景关闭。<br />
 * 场景的开和关都在主线程中进行。
 * User: yunai
 * Date: 13-4-10
 * Time: 下午1:58
 */
@Service
public class NormalSceneService
        extends AbstractSceneService {

    private static final Logger LOGGER = LoggerFactory.getLogger(LoggerFactory.Logger.scene, NormalSceneService.class);

    @Resource
    private OnlineContextService onlineContextService;
    @Resource
    private GameMessageProcessor gameMessageProcessor;
    @Resource
    private GameExecutorService gameExecutorService;

    /**
     * 默认线
     */
    private static final int LINE_DEFAULT = 1;
    /**
     * 检查空闲线程周期，单位（毫秒） TODO 太频繁 log很烦 先设置大点
     */
    private static final int CLEAR_IDLE_SCENE_PERIOD = 10000 * 1000;

    /**
     * 场景线Runner服务Map<br />
     * <场景编号,场景>
     */
    private final Map<Integer, SceneRunner<NormalScene>> sceneRunners;
    /**
     * 场景数量<br />
     * 目前该变量只会在主线程访问，所以不设置volatile
     */
    private int sceneCount = 0;

    private final Runnable CLEAR_IDLE_SCENE_RUNNER = new Runnable() {

        @Override
        public void run() {
            gameMessageProcessor.put(new SysInternalMessage() {

                @Override
                public void execute() {
                    if (sceneCount == 0) {
                        return;
                    }
                    Iterator<SceneRunner<NormalScene>> iterator = sceneRunners.values().iterator();
                    while (iterator.hasNext()) {
                        NormalScene scene = iterator.next().getScene();
                        if (scene.isEmpty() && scene.isMessageEmpty()) {
                            LOGGER.info("[CLEAR_IDLE_SCENE_RUNNER] [scene({}) line({}) is clear].", scene.getSceneId(), scene.getLine());
                            iterator.remove();
                            sceneCount--;
                        }
                    }
                }

                @Override
                public short getCode() {
                    return 0;
                }
            });
        }

    };

    public NormalSceneService() {
        this.sceneRunners = new ConcurrentHashMap<>();
    }

    @PostConstruct
    public void init() {
        LOGGER.info("[init] [START CLEAR_IDLE_SCENE_RUNNER BEGIN].");
        gameExecutorService.scheduleTask(CLEAR_IDLE_SCENE_RUNNER, CLEAR_IDLE_SCENE_PERIOD);
        LOGGER.info("[init] [START CLEAR_IDLE_SCENE_RUNNER SUCCESS].");
    }

    /**
     * [主线程]处理玩家加入场景<br />
     * 若加入成功，则给[场景消息队列]发消息来进行之后的逻辑
     *
     * @param online  在线信息
     * @param sceneId 场景编号
     * @param sceneX  场景坐标X
     * @param sceneY  场景坐标Y
     * @return 加入场景结果
     */
    @MainThread
    public boolean onPlayerEnterNormalScene(Online online, Integer sceneId, Short sceneX, Short sceneY) {
        // 检查场景是否为空，若为空，则无法进行进入场景逻辑
        NormalScene scene = getOrCreateScene(sceneId); // TODO 以后这里检查下sceneId是否是普通场景，防止产生错误的场景
        Player player = online.getPlayer();
        if (scene == null) {
            LOGGER.error("[onPlayerLeaveScene] [online:{} enter scene failure] [scene:{} line:{} is not exist].",
                    player.getId(), sceneId, LINE_DEFAULT);
            return false;
        }
        // 设置玩家所在场景
        online.setScene(scene);
        player.enterScene(sceneId, sceneX, sceneY);
        scene.addOnline(online); // 将玩家添加到场景玩家集合中
        // 给该场景发送进入的消息
        scene.putMessage(new SysPlayerEnterNormalScene(online.getPlayer().getId(), scene.getSceneId(), scene.getLine()));
        return true;
    }

    /**
     * [场景线程]处理玩家进入，并返回是否进入成功<br />
     * 获得不到Online或者Scene会导致进入场景失败
     *
     * @param playerId 玩家编号
     * @param sceneId  进入场景编号
     * @param line     进入场景线号
     * @return 进入场景是否成功
     */
    @SceneThread
    public boolean handleEnterNormalScene(Integer playerId, Integer sceneId, Integer line) {
        Online online = onlineContextService.getPlayer(playerId);
        if (online == null) {
            return false;
        }
        NormalScene scene = getScene(sceneId);
        if (scene == null) {
            LOGGER.error("[handleEnterNormalScene] [online:{} enter scene failure] [scene:{} line:{} is not exist].",
                    online.getPlayer().getId(), sceneId, line);
            return false;
        }
        // 场景通知
        scene.onPlayerEnter(online);
        return true;
    }

    /**
     * [主线程]处理玩家切换场景<br />
     * 首先，设置玩家游戏状态为GamingState.SWITCH_NORMAL_SCENE，玩家离开该场景<br />
     * 之后，玩家通过{@link org.yunai.swjg.server.module.scene.callback.PlayerSwitchNormalSceneCallback}来实现进入场景<br />
     *
     * @param online  在线信息
     * @param sceneId 切换场景编号
     */
//    @MainThread
    @SceneThread
    // TODO 改成场景线程
    public void onPlayerSwitchNormalScene(Online online, Integer sceneId) {
        AbstractScene scene = online.getScene();
        if (scene == null) {
            LOGGER.error("[onPlayerSwitchNormalScene] [online:{} switch scene failure] [online.scene isn't exist].",
                    online.getPlayer().getId());
            return;
        }
        NormalScene targetScene = getOrCreateScene(sceneId);
        if (targetScene == null) {
            LOGGER.error("[onPlayerSwitchNormalScene] [online:{} switch scene failure] [scene:{} line:{} is not exist].",
                    online.getPlayer().getId(), sceneId, online.getScene().getLine());
            return;
        }
        // 设置玩家游戏状态为GamingState.SWITCH_NORMAL_SCENE，玩家离开该场景
        online.setGamingState(GamingState.SWITCH_NORMAL_SCENE);
        super.onPlayerLeaveScene(online, new PlayerSwitchNormalSceneCallback(online.getPlayer().getId(),
                // TODO 这里坐标是乱写的V
                targetScene.getSceneId(), (short) 12, (short) 14)); // PlayerSwitchSceneCallback来实现离开场景后进入切换场景
    }

    /**
     * @param sceneId 场景编号
     * @return 场景
     */
    private NormalScene getScene(Integer sceneId) {
        SceneRunner runner = sceneRunners.get(sceneId);
        return runner != null ? (NormalScene) runner.getScene() : null;
    }

    /**
     * 获得普通场景
     *
     * @param sceneId 场景编号
     * @param line    场景线
     * @return 普通场景
     */
    @Override
    protected NormalScene getScene(Integer sceneId, Integer line) {
        return getScene(sceneId);
    }

    /**
     * [主线程] 获得场景，若场景不存在，则进行场景生成.
     *
     * @param sceneId 场景编号
     * @return 场景
     */
    @MainThread
    private NormalScene getOrCreateScene(Integer sceneId) {
        NormalScene scene = getScene(sceneId);
        if (scene != null) {
            return scene;
        }
        scene = (NormalScene) SceneFactory.createScene(SceneTemplate.get(sceneId));
        scene.setLine(LINE_DEFAULT);
        sceneRunners.put(scene.getSceneId(), new SceneRunner<>(scene));
        sceneCount++;
        return scene;
    }


    /**
     * [场景线程]处理玩家移动
     *
     * @param online   在线信息
     * @param fromSceneX 来源地址坐标x
     * @param fromSceneY 来源地址坐标y
     * @param toSceneX 目标地址坐标x
     * @param toSceneY 目标地址坐标y
     */
    @SceneThread
    public void onPlayerMoveNormalScene(Online online, Short fromSceneX, Short fromSceneY, Short toSceneX, Short toSceneY) {
        AbstractScene scene = online.getScene();
        if (scene == null) {
            LOGGER.error("[onPlayerMoveNormalScene] [online:{}] [scene:{} not exists].", online.getPlayer().getId(),
                    online.getPlayer().getSceneId());
            return;
        }
        // TODO 检查目标点是否在场景内
        // 修改玩家所在坐标
        online.getPlayer().moveSceneXY(fromSceneX, fromSceneY, toSceneX, toSceneY);
        // 场景通知
        scene.onPlayerMove(online, fromSceneX, fromSceneY, toSceneX, toSceneY);
    }

    /**
     * TODO 思考下
     *
     * @return 所有的Runner
     */
    public List<SceneRunner<NormalScene>> getAllSceneRunners() {
        List<SceneRunner<NormalScene>> runnerList = new ArrayList<>();
        for (SceneRunner<NormalScene> runner : sceneRunners.values()) {
            runnerList.add(runner);
        }
        return runnerList;
    }
}
